package main

import (
	"fmt"
	"regexp"
	"strings"
)

type Analysis struct {
	Sql      string
	SplitSql []string
	Error    []string
	KeyWord  []string
}

func NewAnalysis(sql string) *Analysis {
	return &Analysis{Sql: sql}
}

func (this *Analysis) Analysis() []string {
	this.AnalysisComma().AnalysisField().AnalysisKeys().AnalysisEnd()
	return this.Error
}

//1.解析括号
func (this *Analysis) AnalysisComma() *Analysis {
	this.SplitSql = strings.FieldsFunc(this.Sql, func(s rune) bool {
		if s == '(' || s == ')' {
			return true
		} else {
			return false
		}
	})
	if len(this.SplitSql) < 5 {
		this.Error = append(this.Error, "sql语句太简单，不需要检测")
	}
	return this
}

//解析字段
func (this *Analysis) AnalysisField() *Analysis {
	this.addKeyWord()
	//reg := regexp.MustCompile(`[` + "`" + `][\w]+[` + "`" + `][\s\w]+[(]?[\s\w]*?[)]?[\s\w]+["'](.*?)['"],`)
	reg := regexp.MustCompile(`[^a-zA-Z]+[ ]+[` + "`" + `][\w]+[` + "`" + `][\s\w]+[(]?[\s\w]*?[)]?(.*?)[,]+[\w\n]+["']?`)
	fieldList := reg.FindAllString(this.Sql, -1)
	isAuto := false
	for _, v := range fieldList {
		reg = regexp.MustCompile(`[` + "`" + `][\w]+[` + "`" + `]`)
		fieldA := reg.FindAllString(v, -1)
		if len(fieldA) <= 0 {
			continue
		}

		field := strings.ToLower(strings.Replace(fieldA[0], "`", "", -1))
		if this.InArray(field, this.KeyWord) {
			this.Error = append(this.Error, "字段:"+field+"为mysql关键词")
		}
		//AUTO_INCREMENT
		reg = regexp.MustCompile(`\bAUTO_INCREMENT\b|\bauto_increment\b`)
		autoList := reg.FindAllString(v, -1)
		if len(autoList) > 0 {
			isAuto = true
			continue
		}

		//字段默认值
		reg = regexp.MustCompile(`\bdefault\b|\bDEFAULT\b`)
		defaultList := reg.FindAllString(v, -1)
		if len(defaultList) <= 0 {
			this.Error = append(this.Error, "字段:"+field+"没有默认值")
		}
		// 备注信息
		reg = regexp.MustCompile(`\bcomment\b|\bCOMMENT\b`)
		commentList := reg.FindAllString(v, -1)
		if len(commentList) <= 0 {
			this.Error = append(this.Error, "字段:"+field+"没有备注信息")
		}
	}
	if !isAuto {
		this.Error = append(this.Error, "主键没有设置自增")
	}
	return this
}

//解析索引
func (this *Analysis) AnalysisKeys() *Analysis {

	reg := regexp.MustCompile(`[\w\s]+[Kk][Ee][Yy][\s]+[(]`)
	keyList := reg.FindAllString(this.Sql, -1)
	if len(keyList) <= 0{
		this.Error = append(this.Error, "没有设置主键")
	}
	reg = regexp.MustCompile(`[Kk][Ee][Yy][\s][` + "`" + `]?[\w]+[` + "`" + `]`)
	keyList = reg.FindAllString(this.Sql, -1)
	if len(keyList) > 5 {
		this.Error = append(this.Error, "索引个数大于5个")
	}
	return this
}

//解析最后部分，比如自增数字等
type DefaultMap struct {
	value string
	error string
	empty string
}

func (this *Analysis) AnalysisEnd() *Analysis {
	var endSplit []string
	reg := regexp.MustCompile(`([\w\s]+=[\s]*["'\w]*[^\x00-\xff]*?['"]*)`)
	endSplit = reg.FindAllString(this.Sql, -1)

	reg = regexp.MustCompile(`[\w]+[\s]*?=[\s]*['"](.*?)['"]`)
	endSplitComment := reg.FindAllString(this.Sql, -1)
	if len(endSplitComment) > 0 {
		endSplit = append(endSplit, endSplitComment[0])
	}

	var endFieldSplit []string
	var endFieldMap = make(map[string]string, 0)
	for _, v := range endSplit {
		str := strings.Replace(v, " ", "", -1)
		endFieldSplit = strings.FieldsFunc(str, func(s rune) bool {
			if s == '=' {
				return true
			} else {
				return false
			}
		})
		if len(endFieldSplit) > 1 {
			key := strings.ToLower(endFieldSplit[0])
			value := strings.ToLower(endFieldSplit[1])
			if key == "comment" {
				value = strings.Replace(value, `'`, "", -1)
				value = strings.Replace(value, `"`, "", -1)
			}
			endFieldMap[key] = value
		}
	}
	fmt.Println(endFieldMap, endSplit)
	var defaultMap = make(map[string]DefaultMap, 0)
	defaultMap["engine"] = DefaultMap{value: "innodb", error: "数据库存储引擎选择的不是innodb",empty:"数据库存储引擎没有设置"}
	defaultMap["auto_increment"] = DefaultMap{value: "1", error: "auto_increment的值不是1",empty:"auto_increment没有设置"}
	defaultMap["defaultcharset"] = DefaultMap{value: "utf8", error: "字符集不是utf8",empty:"字符集没有设置"}
	defaultMap["comment"] = DefaultMap{value: "", error: "表备注信息不能为空",empty:"表备注信息没有设置"}
	for k, v := range defaultMap {
		if endField, ok := endFieldMap[k]; ok {
			if k == "defaultcharset" {
				if endField != "utf8" && endField != "utf8mb4" {
					this.Error = append(this.Error, v.error)
				}
				continue
			} else if k == "comment" {
				if endField == "" {
					this.Error = append(this.Error, v.error)
				}
				continue
			}
			if v.value != endField {
				this.Error = append(this.Error, v.error)
			}
		} else {
			this.Error = append(this.Error, v.empty)
		}
	}
	return this
}

func (this *Analysis) addKeyWord() {
	this.KeyWord = []string{"name ", "code", "status", "add", "all", "alter", "analyze", "and", "as", "asc", "asensitive", "before", "between", "bigint", "binary", "blob", "both", "by", "call", "cascade", "case", "change", "char", "character", "check", "collate", "column", "condition", "connection", "constraint", "continue", "convert", "create", "cross", "current_date", "current_time", "current_timestamp", "current_user", "cursor", "database", "databases", "day_hour", "day_microsecond", "day_minute", "day_second", "dec", "decimal", "declare", "default", "delayed", "delete", "desc", "describe", "deterministic", "distinct", "distinctrow", "div", "double", "drop", "dual", "each", "else", "elseif", "enclosed", "escaped", "exists", "exit", "explain", "false", "fetch", "float", "float4", "float8", "for", "force", "foreign", "from", "fulltext", "goto", "grant", "group", "having", "high_priority", "hour_microsecond", "hour_minute", "hour_second", "if", "ignore", "in", "index", "infile", "inner", "inout", "insensitive", "insert", "int", "int1", "int2", "int3", "int4", "int8", "integer", "interval", "into", "is", "iterate", "join", "key", "keys", "kill", "label", "leading", "leave", "left", "like", "limit", "linear", "lines", "load", "localtime", "localtimestamp", "lock", "long", "longblob", "longtext", "loop", "low_priority", "match", "mediumblob", "mediumint", "mediumtext", "middleint", "minute_microsecond", "minute_second", "mod", "modifies", "natural", "not", "no_write_to_binlog", "null", "numeric", "on", "optimize", "option", "optionally", "or", "order", "out", "outer", "outfile", "precision", "primary", "procedure", "purge", "raid0", "range", "read", "reads", "real", "references", "regexp", "release", "rename", "repeat", "replace", "require", "restrict", "return", "revoke", "right", "rlike", "schema", "schemas", "second_microsecond", "select", "sensitive", "separator", "set", "show", "smallint", "spatial", "specific", "sql", "sqlexception", "sqlstate", "sqlwarning", "sql_big_result", "sql_calc_found_rows", "sql_small_result", "ssl", "starting", "straight_join", "table", "terminated", "then", "tinyblob", "tinyint", "tinytext", "to", "trailing", "trigger", "true", "undo", "union", "unique", "unlock", "unsigned", "update", "usage", "use", "using", "utc_date", "utc_time", "utc_timestamp", "values", "varbinary", "varchar", "varcharacter", "varying", "when", "where", "while", "with", "write", "x509", "xor", "year_month", "zerofill"}
}

func (this *Analysis) InArray(key string, arr []string) bool {
	for _, v := range arr {
		if key == v {
			return true
		}
	}
	return false
}
